#!/usr/bin/ruby -rrubygems
# == Synopsis
#
# ppVIS: Visualizes network packet fiels in a parallel plot
#
# == Usage
#
# ppVIS [OPTIONS] 
#
# -i, --interface:
#     interface that is used to receive packets 
# -f, --filter: 
#     pcap filter expression
# -b, --bars:
#     string whith fields to plot separated by a whitespace
# -r, --handler:
#     name of the handler to use
# -u, -h, --usage, --help:
#     prints this help message
# -a, --address:
#     address to listen on
# -p, --port:
#     port    to listen on
# -s, --show_bars:
#     show all possible fields
#
# == EXAMPLE
# ./ppvis.rb -i eth0 -b "ip_src sport dport ip_dst"
require "getopt/long"
require 'rdoc/usage'
require "opengl"
require "glut"
require "mathn"
require "pp"
require 'thread'
require "pcap"
require "ipaddr"

include Gl,Glu,Glut


require "lib/helper"
require "lib/statistics"

require "lib/handlermanager"
require "lib/handler/pcap"
require "lib/handler/netflow"

require "lib/gl/base"
require "lib/gl/connection"
require "lib/gl/connections/tcp"
require "lib/gl/connections/udp"
require "lib/gl/connections/icmp"
require "lib/gl/connections/netflow"
require "lib/gl/pplot"

require "lib/bases/pcap"
require "lib/bases/netflow"


semaphore = Mutex.new
last_redisplay = Time.now


SECONDS_TO_FADEOUT = 2
FADING_STEPS       = SECONDS_TO_FADEOUT*20 

TEXTURE = []

$window_height = 500
$window_width  = 500

$connections = [
]

$zoom = 0.4

display = Proc.new {
   STATISTICS.display($connections)
   glClear GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT 

   list = glGenLists(1)
   glNewList(list, GL_COMPILE)

   $connections.each do |conn|
      conn.display( PLOT.order )
   end
   
   glColor(1.0, 1.0, 1.0)

   PLOT.display
   glEndList()

   glCallList(list)
   glutSwapBuffers();
   glFlush();

   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   if ($window_width <= $window_height)
      glOrtho(0.0-$zoom, 0.75+$zoom,       0.0-$zoom, (1.0*$window_height/$window_width)+$zoom, -10.0, 10.0);
   else
      glOrtho(0.0-$zoom, (0.75*$window_width/$window_height)+$zoom, 0.0-$zoom, 1.0+$zoom, -10.0, 10.0);
   end
   glMatrixMode(GL_MODELVIEW)
   glClear GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT 

   $connections = $connections.find_all{|x| !x.timeout? }
}

redisplay = Proc.new {
    GLUT.PostRedisplay # if connections.size > 0
    last_redisplay = Time.now
}


reshape = Proc.new {|w, h|
   glViewport(0, 0,  w, h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   if (w <= h)
      glOrtho(0.0-$zoom, 0.75+$zoom,       0.0-$zoom, (1.0*h/w)+$zoom, -10.0, 10.0);
   else
      glOrtho(0.0-$zoom, (0.75*w/h)+$zoom, 0.0-$zoom, 1.0+$zoom, -10.0, 10.0);
   end
   glMatrixMode(GL_MODELVIEW)
   glClear GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT 
}

# /* ARGSUSED1 */
keyboard = Proc.new {|key, x, y|
   case (key)
      when 't'[0],'T'[0]
         $leftFirst = !$leftFirst;
         GLUT.PostRedisplay();
      when 27  #  /*  Escape key  */
         exit(0);
      when 43 # + Key
         $zoom -= 0.1
      when 45 # - Key
         $zoom += 0.1
      else
#          pp [key, x, y]
   end
}

def init
   glClearColor(0.0, 0.0, 0.0, 0.0);

   glEnable(GL_LINE_SMOOTH);
   glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);

   glDisable(GL_LIGHTING)

   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   glOrtho(0.0, 0.76, 0.0, 1.0, -1.0, 1.0);

   glEnable(GL_TEXTURE_2D);
   glEnable(GL_DEPTH_TEST);
   glEnable(GL_NORMALIZE);
   glShadeModel(GL_FLAT);
   glClear  GL_COLOR_BUFFER_BIT
end

begin
   opt = Getopt::Long.getopts(
      ["--interface",         "-i", Getopt::REQUIRED],
      ["--filter",            "-f", Getopt::REQUIRED],
      ["--bars",              "-b", Getopt::REQUIRED],
      ["--handler",           "-r", Getopt::REQUIRED],
      ["--usage",             "-u", Getopt::BOOLEAN],
      ["--help",              "-h", Getopt::BOOLEAN],
      ["--address",           "-a", Getopt::REQUIRED],
      ["--port",              "-p", Getopt::REQUIRED],
      ["--show_bars",         "-s", Getopt::BOOLEAN]
   )
  
   if ARGV.size == 0 or opt["usage"] or opt["help"]
       puts RDoc::usage

       exit 0
   end

   if opt["show_bars"]
       puts "List of possible bars per handler"
       puts "-"*30
       puts PLOT.bar_to_s
       exit 0
   end

   if opt["interface"]
       interface = opt["interface"]
       puts "[ppVIS] set interface to #{interface}"
   else
       interface = "eth0"
       puts "[ppVIS] switching to default interface #{interface}"
   end

   if opt["filter"]
       filter = opt["filter"]
       puts "[ppVIS] set filter to #{filter}"
   end

   if opt["address"]
       address = opt["address"]
       puts "[ppVIS] selected address #{address}"
   else
       address = "127.0.0.1"
   end

   if opt["port"]
       port = opt["port"]
       puts "[ppVIS] selected port #{port}"
   else
       port = "5555"
   end

   handler = HandlerManager.new
   handler.add_handler( "pcap",    PcapHandler.new(interface, filter) )
   handler.add_handler( "netflow", NetflowHandler.new(address, port) )

   if opt["handler"]
       handler.set_handler_by_name opt["handler"]
       puts "[ppVIS] selected handler #{opt["handler"]}"
   else
       puts "[ppVIS] using default handler: pcap"
       handler.set_handler_by_name "pcap" 
   end

   if opt["bars"]
       order  = opt["bars"].split(/\s+/)

       if order.size > 0 
           if order.size < 1
               raise "NEED AT LEAST TWO BASES"
           end
           puts "[ppVIS] set order: #{order.join(",")}"
           PLOT.set_order order
       else
           PLOT.set_order handler.get_default_bars
       end
   end

   PLOT.get_ready

   GLUT.Init
   GLUT.InitDisplayMode(GLUT::DOUBLE | GLUT::RGB);
   GLUT.InitWindowSize($window_width, $window_height); 
   GLUT.InitWindowPosition(100, 100);
   GLUT.CreateWindow("ppVIS superAlpha");
   init();
   GLUT.DisplayFunc(display);
   GLUT.ReshapeFunc(reshape);
   GLUT.IdleFunc(redisplay);
   GLUT.KeyboardFunc(keyboard);


    #PLOT.add_base BaseLine.new("src_ip",   [0, 2.power!(32)], range_gen("10.1.1.1/24") 

    Thread.new{
        begin
            #PLOT.get_base("ip_src").filter = range_gen("10.123.101.0/24")
            #PLOT.get_base("ip_dst").filter = range_gen("10.123.101.0/24")

            handler.run()
        rescue Exception => e
         #   pp e
	 #   pp e.backtrace
            exit( -1 )
        end
    }
    GLUT.MainLoop();
end
